/*
 Copyright (C) 2010-2011. 
 Ezequiel Palumbo, Calipsoide FOSS Foundation.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License
 along with this program.  If not, see http://www.gnu.org/licenses/. 
 
 Email: ehpalumbo@gmail.com 
*/

package org.calipsoide.gwtspring.support;

import java.util.Enumeration;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.context.ServletContextAware;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;

import com.google.gwt.user.client.rpc.RemoteService;

/**
 * Spring MVC web adapter for GWT's RPC request handling.<br>
 * <br>
 * In order to make this HandlerAdapter works, you have to register it as a bean 
 * in the current Spring's application context. If you use XML files to declare
 * your bean definitions, you may want to include the following line:<br>
 * <br>
 * {@code <bean id="handlerAdapter" class="org.calipsoide.gwtspring.support.GWTHandlerAdapter" />}
 * <br><br>
 * For security purposes, to avoid unexpected errors while invoking methods over 
 * handlers, the adapter only supports classes annotated with {@link GWTRemoteService}, 
 * in the case they don't implement a sub-interface of the classical {@link RemoteService} 
 * interface.
 * 
 * @author Ezequiel Palumbo (ehpalumbo)
 * @since 1.0
 * 
 */
public class GWTHandlerAdapter implements HandlerAdapter, ServletContextAware {

	/**
	 * Thread local variable which exposes the handler given by Spring MVC's 
	 * {@link org.springframework.web.servlet.DispatcherServlet} for each request.
	 */
	private final ThreadLocal<Object> perRequestHandler;
	
	/**
	 * Delegate for processing GWT RPCs and related stuff.
	 */
	private GWTServletDelegate gwtDelegate;
	
	
	/**
	 * Default constructor.
	 */
	public GWTHandlerAdapter() {
		super();
		this.gwtDelegate = null;
		this.perRequestHandler = new ThreadLocal<Object>();
	}

	
	/**
	 * Returns the delegate for processing GWT RPCs and related stuff.
	 * 
	 * @return The GWT Delegate. 
	 */
	protected final GWTServletDelegate getGwtDelegate() {
		return this.gwtDelegate;
	}
	
	
	/**
	 * Exposes the handler given by Spring MVC's
	 * {@link org.springframework.web.servlet.DispatcherServlet} for each request.
	 * 
	 * @return The registered handler to attend this request.
	 */
	protected final Object getHandlerForCurrentRequest() {
		return this.perRequestHandler.get();
	}
	
	
	/**
	 * Indicates if this adapter supports the defined handler. This is used by 
	 * Spring MVC before passing the current <code>HttpServletRequest</code>, so
	 * we can decide it by 'typing' conditions (RTTI).<br>
	 * The behavior here is to accept handlers that implement {@link RemoteService}
	 * or have been annotated with {@link GWTRemoteService}.
	 * 
	 * @param paramObject A handler given by Spring
	 * @return We can support this handler?
	 * 
	 */
	public boolean supports(Object paramObject) {
		Class<?> objectClass = paramObject.getClass();
		return (RemoteService.class.isAssignableFrom(objectClass))
				|| (objectClass.getAnnotation(GWTRemoteService.class) != null);
	}

	
	/**
	 * Handles incoming <code>HttpServletRequest</code> to make the action. 
	 * 
	 * @param paramHttpServletRequest The current request.
	 * @param paramHttpServletResponse The current response.
	 * @param paramObject The handler we will use to invoke RPC over.
	 * @return <i>null</i> (we won't use any view rendering server-based system).
	 *   
	 */
	public ModelAndView handle(HttpServletRequest paramHttpServletRequest,
			HttpServletResponse paramHttpServletResponse, Object paramObject)
			throws Exception {
		checkAdapterState();
		perRequestHandler.set(paramObject);
		this.gwtDelegate.doPost(paramHttpServletRequest, paramHttpServletResponse);
		perRequestHandler.remove();
		return null;
	}

	
	/**
	 * {@inheritDoc}
	 */
	public long getLastModified(HttpServletRequest paramHttpServletRequest,
			Object paramObject) {
		return this.gwtDelegate.getLastModified(paramHttpServletRequest);
	}

	
	/**
	 * For dependency injection purposes. We need to know the real servlet context,
	 * because GWT RPC stuff is based on servlets. Otherwise, we will get many errors.
	 * 
	 * @param servletContext The servlet context.
	 * @see com.google.gwt.user.server.rpc.RemoteServiceServlet
	 * @see javax.servlet.ServletContext
	 * 
	 */
	public void setServletContext(ServletContext servletContext) {
		try {
			this.gwtDelegate = createGWTServletDelegate();
			this.gwtDelegate.init(new GWTDelegateServletConfig(servletContext));
		} catch (ServletException se) {
			throw new IllegalStateException(se);
		}
	}
	
	
	/**
	 * This factory method returns a {@link GWTServletDelegate}. 
	 * Subclasses may override it in order to change some adapter behaviors 
	 * related with GWT RPC processing.
	 * 
	 * @return A delegate for RPC dealing.
	 * 
	 */
	protected GWTServletDelegate createGWTServletDelegate() {
		return new GWTServletDelegate();
	}

	
	private void checkAdapterState() {
		if (this.gwtDelegate == null) {
			throw new IllegalStateException("This adapter needs the current " + 
					"servlet context in order to work.");
		}
	}
	
	
	/**
	 * Delegate class for GWT RPC processing. Its responsibility is to deal with incoming RPCs.<br>
	 * It's a subclass of {@link GWTDispatcherServlet}, so all the logic to do such job 
	 * will be part of the adapter flow at runtime.
	 * 
	 * @author Ezequiel Palumbo (ehpalumbo)
	 *
	 */
	protected class GWTServletDelegate extends GWTDispatcherServlet {

		private static final long serialVersionUID = 1L;

		
		/**
		 * Default constructor.
		 */
		public GWTServletDelegate() {
			super();
		}

		
		/**
		 * Returns the registered remote service implementation that Spring 
		 * gave to the enclosing adapter to handle.
		 * 
		 * @return The registered target implementation.
		 */
		@Override
		protected Object findRemoteServiceForCurrentRequest() {
			return getHandlerForCurrentRequest();
		}
		
		
		/**
		 * Convenient override for letting Spring MVC to know about 
		 * <code>lastModified</code> information.
		 * 
		 * @param req An incoming request.
		 * @return <code>lastModified</code> resource's date.
		 * @see javax.servlet.HttpServlet#getLastModified(HttpServletRequest)
		 */
		@Override
		protected long getLastModified(HttpServletRequest req) {
			return super.getLastModified(req);
		}

	}

	
	private class GWTDelegateServletConfig implements ServletConfig {

		private ServletContext servletContext;
		
		public GWTDelegateServletConfig(ServletContext servletContext) {
			super();
			this.servletContext = servletContext;
		}
		
		public String getInitParameter(String arg0) {
			return null;
		}

		@SuppressWarnings("unchecked")
		public Enumeration getInitParameterNames() {
			return new Enumeration() {
				public boolean hasMoreElements() {
					return false;
				}
				public Object nextElement() {
					return null;
				};
			};
		}

		public ServletContext getServletContext() {
			return this.servletContext;
		}

		public String getServletName() {
			return "EmbedWrapper4GWTRemoteServices";
		}
		
	}
	
}
